// 2024/8/30: 首个版本
// 2024/11/12: 修正ClosePort触发异常
// 2025/1/21: 接口化
// 2025/6/3: 重命名为ReceiveAnyPacketTimeout
// 2025/7/22: 处理通讯协议版本不一致的情况
// 2025/7/22: ReceivePort确保多线程安全

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using ASEva.Utility;

namespace RemoteConsole
{
    interface SocketHandler
    {
        Task<bool> Open(String ip, ushort port1, ushort port2);
        Task Close();
        bool IsConnected();
        Task<bool> Send(byte[] binary, bool optional);
        ReceiveBinary[] Receive();
    }

    class ClientDualPort : SocketHandler
    {
        public async Task<bool> Open(String serverIP, ushort serverInPort, ushort serverOutPort)
        {
            if (targetThreadID < 0) targetThreadID = Thread.CurrentThread.ManagedThreadId;
            else AssertCurrentThread();
            
            if (open)
            {
                if (IsConnected()) return false;
                else open = false;
            }

            inID = serverIP + ":" + serverInPort;
            outID = serverIP + ":" + serverOutPort;
            while (true)
            {
                await Task.Delay(1);

                var now = DateTime.Now;
                bool reopenInOK = !lastCloseTime.ContainsKey(inID) || (now - lastCloseTime[inID]).TotalMilliseconds > ReopenIntervalTime;
                bool reopenOutOK = !lastCloseTime.ContainsKey(outID) || (now - lastCloseTime[outID]).TotalMilliseconds > ReopenIntervalTime;
                if (reopenInOK && reopenOutOK) break;
            }

            var results = await Task.WhenAll(sendPort.Open(serverIP, serverInPort), receivePort.Open(serverIP, serverOutPort));

            bool ok = results[0] && results[1];
            if (!ok)
            {
                await Task.WhenAll(sendPort.Close(), receivePort.Close());

                var now = DateTime.Now;
                if (inID != null) lastCloseTime[inID] = now;
                if (outID != null) lastCloseTime[outID] = now;

                return false;
            }

            open = true;
            return ok;
        }

        public bool IsConnected()
        {
            return sendPort.Connected && receivePort.Connected;
        }

        public async Task Close()
        {
            AssertCurrentThread();

            if (!open) return;
            open = false;

            await Task.WhenAll(sendPort.Close(), receivePort.Close());

            var now = DateTime.Now;
            if (inID != null) lastCloseTime[inID] = now;
            if (outID != null) lastCloseTime[outID] = now;
        }

        public async Task<bool> Send(byte[] binary, bool optional)
        {
            AssertCurrentThread();

            var ok = await sendPort.Send(binary, optional);
            if (!ok && !optional) await Close();
            return ok;
        }

        public ReceiveBinary[] Receive()
        {
            AssertCurrentThread();
            return receivePort.Receive();
        }

        private static void AssertCurrentThread()
        {
            if (Thread.CurrentThread.ManagedThreadId != targetThreadID) throw new Exception("Invoked by wrong thread");
        }

        private class Port
        {
            public bool Connected { get => socket != null && socket.Connected; }

            public async Task<bool> Open(String serverIP, ushort serverPort)
            {
                if (socket != null) return false;

                IPAddress ipAddress = null;
                if (!IPAddress.TryParse(serverIP, out ipAddress)) return false;

                Socket newSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
                newSocket.NoDelay = true;
                IAsyncResult asyncResult = null;
                try
                {
                    asyncResult = newSocket.BeginConnect(ipAddress, serverPort, null, null);
                    asyncResult.AsyncWaitHandle.WaitOne(10); // 立即失败时可避免Mobile调试卡死
                }
                catch (Exception ex)
                {
                    Dump.Exception(ex);
                    newSocket.Close();
                    return false;
                }

                var t0 = DateTime.Now;
                while (true)
                {
                    await Task.Delay(1);
                    AssertCurrentThread();

                    if (asyncResult.IsCompleted)
                    {
                        if (newSocket.Connected)
                        {
                            newSocket.EndConnect(asyncResult);
                            break;
                        }
                        else
                        {
                            newSocket.Close();
                            return false;
                        }
                    }

                    var now = DateTime.Now;
                    if (now < t0) t0 = now;
                    if ((now - t0).TotalMilliseconds >= ConnectTimeout)
                    {
                        newSocket.Close();
                        return false;
                    }
                }

                socket = newSocket;

                shouldEnd = false;
                loopTask = loopRun();

                return true;
            }

            public async Task Close()
            {
                if (loopTask != null)
                {
                    shouldEnd = true;
                    await loopTask;
                }

                await ClosePort();
            }

            protected virtual async Task ClosePort()
            {
                loopTask = null;

                if (socket == null) return;

                try
                {
                    var asyncResult = socket.BeginDisconnect(false, null, null);
                    while (true)
                    {
                        await Task.Delay(1);
                        if (asyncResult.IsCompleted)
                        {
                            if (socket != null) socket.EndDisconnect(asyncResult);
                            break;
                        }
                    }
                }
                catch (Exception ex) { Dump.Exception(ex); }
                socket = null;
            }

            protected virtual Task loopRun()
            {
                return Task.CompletedTask;
            }

            protected Socket socket = null;
            protected bool shouldEnd = false;
            private Task loopTask = null;

            private const int ConnectTimeout = 600;
        }

        private class SenderPort : Port
        {
            protected override async Task ClosePort()
            {
                await base.ClosePort();
                sendPacket.Reset();
            }

            public async Task<bool> Send(byte[] binary, bool optional)
            {
                if (!Connected) return false;
                if (binary == null || binary.Length == 0) return true;

                var t0 = DateTime.Now;
                var fullBinary = sendPacket.Generate(binary);

                IAsyncResult asyncResult = null;
                var t1 = DateTime.Now;
                try
                {
                    asyncResult = socket.BeginSend(fullBinary, 0, fullBinary.Length, SocketFlags.None, null, null);
                }
                catch (Exception ex)
                {
                    Dump.Exception(ex);
                    if (!optional) await Close();
                    return false;
                }

                while (true)
                {
                    if (asyncResult.IsCompleted) break;

                    await Task.Delay(1);
                    AssertCurrentThread();

                    var now = DateTime.Now;
                    if (now < t1) t1 = now;
                    if ((now - t1).TotalMilliseconds >= (optional ? OptionalSendTimeout : RequiredSendTimeout))
                    {
                        if (!optional)
                        {
                            await Close();
                            return false;
                        }
                        else break;
                    }
                }

                costTimeAccum += Math.Max(0, (DateTime.Now - t0).TotalMilliseconds);
                if (costTimeAccum > SendCostTimeAccumLimit)
                {
                    costTimeAccum = 0;
                    await Task.Delay(SendBreakTime);
                }

                return true;
            }

            protected override async Task loopRun()
            {
                while (!shouldEnd)
                {
                    await Task.Delay(HeartBeatPeriod);
                    AssertCurrentThread();

                    if (!Connected)
                    {
                        await ClosePort();
                        return;
                    }

                    var heartBeatBinary = sendPacket.GenerateHeartBeat();

                    IAsyncResult asyncResult = null;
                    var t0 = DateTime.Now;
                    try
                    {
                        asyncResult = socket.BeginSend(heartBeatBinary, 0, heartBeatBinary.Length, SocketFlags.None, null, null);
                    }
                    catch (Exception ex)
                    {
                        Dump.Exception(ex);
                        await ClosePort();
                        return;
                    }

                    while (true)
                    {
                        if (asyncResult.IsCompleted) break;

                        await Task.Delay(1);
                        AssertCurrentThread();

                        var now = DateTime.Now;
                        if (now < t0) t0 = now;
                        if ((now - t0).TotalMilliseconds >= RequiredSendTimeout)
                        {
                            await ClosePort();
                            return;
                        }
                    }
                }
            }

            private SendPacket sendPacket = new SendPacket();
            private double costTimeAccum = 0;

            private const int RequiredSendTimeout = 1000;
            private const int OptionalSendTimeout = 100;
            private const int HeartBeatPeriod = 200;
            private const int SendCostTimeAccumLimit = 50;
            private const int SendBreakTime = 10;
        }

        private class ReceivePort : Port
        {
            protected override async Task ClosePort()
            {
                await base.ClosePort();
                lock (receiveStreamLock)
                {
                    receiveStream = new ReceiveStream();
                }
            }

            public ReceiveBinary[] Receive()
            {
                lock (receiveStreamLock)
                {
                    return receiveStream.Parse();
                }
            }

            protected override async Task loopRun()
            {
                var receiveBuffer = new byte[ReceiveBufferSize];
                var lastReceiveTime = DateTime.Now;
                while (!shouldEnd)
                {
                    await Task.Delay(1);
                    AssertCurrentThread();

                    if (!checkConnection(ref lastReceiveTime))
                    {
                        await ClosePort();
                        return;
                    }

                    IAsyncResult asyncResult = null;
                    try
                    {
                        asyncResult = socket.BeginReceive(receiveBuffer, 0, receiveBuffer.Length, SocketFlags.None, null, null);
                    }
                    catch (Exception ex)
                    {
                        Dump.Exception(ex);
                        await ClosePort();
                        return;
                    }
                    
                    int receivedSize = 0;
                    while (!shouldEnd)
                    {
                        await Task.Delay(1);
                        AssertCurrentThread();

                        if (!checkConnection(ref lastReceiveTime))
                        {
                            await ClosePort();
                            return;
                        }

                        if (asyncResult.IsCompleted)
                        {
                            receivedSize = socket.EndReceive(asyncResult);
                            break;
                        }
                    }

                    if (receivedSize > 0)
                    {
                        lock (receiveStreamLock)
                        {
                            receiveStream.Append(receiveBuffer.AsSpan(0, receivedSize).ToArray());
                            if (!receiveStream.VersionMismatch) lastReceiveTime = DateTime.Now;
                        }
                    }
                }
            }

            private bool checkConnection(ref DateTime lastReceiveTime)
            {
                if (!Connected) return false;

                var t0 = DateTime.Now;
                if (t0 < lastReceiveTime) lastReceiveTime = t0;
                if ((t0 - lastReceiveTime).TotalMilliseconds > ReceiveAnyPacketTimeout) return false;

                return true;
            }

            private ReceiveStream receiveStream = new ReceiveStream();
            private object receiveStreamLock = new object();

            private const int ReceiveBufferSize = 100000;
            private const int ReceiveAnyPacketTimeout = 1000;
        }

        
        private SenderPort sendPort = new SenderPort();
        private ReceivePort receivePort = new ReceivePort();
        private bool open = false;
        private String inID = null, outID = null;
        private static Dictionary<String, DateTime> lastCloseTime = new Dictionary<string, DateTime>();
        private static int targetThreadID = -1;

        private const int ReopenIntervalTime = 2000;
    }
}